package com.zhuiYun.project.api.seckill.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.rabbitmq.client.Channel;
import com.zhuiYun.project.api.order.entity.CommodityArr;
import com.zhuiYun.project.api.order.entity.SubmitOrderVo;
import com.zhuiYun.project.api.order.service.OrderService;
import com.zhuiYun.project.common.response.CommonConstants;
import com.zhuiYun.project.common.response.CommonResult;
import com.zhuiYun.project.config.RabbitMQConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author: HKY
 * @date: 2023年04月14日 21:02
 * @ClassName: MqOrderServiceImpl
 * @Description: Mq订单服务 主要实现了MQ消息队列的监听与消费，以及数据库操作
 * 这是一个基于Spring Boot和RabbitMQ实现的订单服务，主要实现了MQ消息队列的监听与消费，以及数据库操作等。
 * 该类中的saveOrder方法监听RabbitMQ的订单队列，当有新的消息到来时，将消息转化为SubmitOrderVo对象，
 * 然后调用orderService.submitCommodityOrder方法创建订单信息。
 * 如果创建订单失败，会将已经扣减的商品库存回滚，并向消息队列发送NACK消息，请求重新投递消息；
 * 如果创建订单成功，则向消息队列发送ACK消息，确认消费成功。
 * 同时，redisRollback方法可以对商品库存进行回滚处理。
 *
 */

@Service
@Slf4j
public class MqOrderServiceImpl {

    @Autowired
    OrderService orderService;
    @Resource
    private RedisTemplate redisTemplate;
    /*
     * MQ监听订单消息队列，并消费
     */
    @RabbitListener(queues = RabbitMQConfig.ORDER_QUEUE ,containerFactory = "rabbitListenerContainerFactory")
//    来指定 saveOrder 方法使用一个新的事务。这样做法可以保证消费消息时出现异常时，只回滚当前消息的操作，而不影响前面消息已经成功消费的操作。
    @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
    public void saveOrder(Message message, Channel channel) throws IOException {
        try {
            SubmitOrderVo submitOrderVo = JSONObject.parseObject(new String(message.getBody())).toJavaObject(SubmitOrderVo.class);
            String successFlag = submitOrderVo.getSuccessFlag();
            //有消费成功标识代表这条消息消费过直接返回
            if (redisTemplate.hasKey(successFlag)){
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
                return;
            }
            List<CommodityArr> commodityArrs = JSON.parseArray(submitOrderVo.getProductArr(), CommodityArr.class);
            CommonResult commonResult = new CommonResult();
            commonResult.setFlag(false);
            StringBuffer commodityNameList = null;
            try {
                LocalDateTime now = LocalDateTime.now();
                log.info("订单创建时间{}", now.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss EEE a")));
                //调用数据库orderService创建订单信息 480ms左右
                commonResult = orderService.submitCommodityOrder(submitOrderVo);
            }catch (Exception e){
                commodityNameList = redisRollback(commodityArrs);
                log.info("消费订单时调用数据库创建订单信息失败，订单用户为：{}，用户ID为：{}，商品名称为：{}，异常信息{}",
                        submitOrderVo.getUserId(), submitOrderVo.getUserId(), commodityNameList, e);
                //basicNack 失败确认。
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), true, false);
                return;
            }
            log.info("submitCommodityOrder方法消费结果"+commonResult.toString());
            //事务判断
            if (commonResult.isFlag()) {
                redisTemplate.opsForValue().set(successFlag, true,1, TimeUnit.HOURS);
                StringBuffer extApiKey = new StringBuffer();
                extApiKey.append(CommonConstants.COMMODITY_SECKILL_SUCCESS+":"+submitOrderVo.getUserId()+":");
                for (CommodityArr commodityArr : commodityArrs) {
                    extApiKey.append(commodityArr.getId());
                }
                log.info("saveOrder()的成功标识key::"+extApiKey);
                redisTemplate.opsForValue().set(String.valueOf(extApiKey), commonResult);
                //消费成功
                log.info("消费订单成功，订单用户为：{}，详细信息为：{}", submitOrderVo.getUserId(), submitOrderVo);
                //basicAck 成功确认，使用此回执方法后，消息会被rabbitmq broker删除。
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
            } else {
                commodityNameList = redisRollback(commodityArrs);
                log.info("消费订单失败，订单用户为：{}，用户ID为：{}，商品名称为：{}",
                        submitOrderVo.getUserId(), submitOrderVo.getUserId(), commodityNameList);
                //basicNack 失败确认。
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), true, false);
            }
        } catch (Exception e) {
            log.error("处理 MQ 消息时发生异常", e);
            //basicNack 失败确认。
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), true, false);
        }

    }
    /**
     * @author: HKY
     * @date: 2023/4/19 10:50
     * @Description: Redis 库存回滚处理 并返回用户秒杀所有商品
     */

    private StringBuffer redisRollback(List<CommodityArr> commodityArrs){
        //商品名称
        StringBuffer commodityNameList = new StringBuffer();
        for (CommodityArr commodityArr : commodityArrs) {
            commodityNameList.append(commodityArr.getCommodityName()+"、");
            if (redisTemplate.hasKey(CommonConstants.COMMODITY_SECKILL_STOCK + commodityArr.getId())){
                redisTemplate.opsForValue().increment(CommonConstants.COMMODITY_SECKILL_STOCK + commodityArr.getId());
            } else if (redisTemplate.hasKey(CommonConstants.COMMODITY_STOCK + commodityArr.getId())){
                redisTemplate.opsForValue().increment(CommonConstants.COMMODITY_STOCK+commodityArr.getId());
            }
        }
        commodityNameList = commodityNameList.deleteCharAt(commodityNameList.length() - 1);
        return commodityNameList;
    }
}
